<?php

namespace Core\MVC;

class RouteNode {

    public $parent;
    public $nodes = array();
    public $action;
    public $route;
}

class Router implements IRouter {

    const PATH_SEPARATOR = '/';
    const METHOD_DELIMITER = ":";

    private $routes;
    private $container;
    private $preprocessedRoutes = array();
    private $reverseRoutes = array();

    public function init() {
        foreach ($this->routes as $route => $action) {
            $this->addRoute($route, $action);
        }
    }

    public function resolve($requestPath, $requestMethod) {
        $node = $this->preprocessedRoutes[$requestMethod];
        if ($node == null) {
            throw new NoRouteException($requestPath, $requestMethod);
        }

        $args = array();
        $path = explode(self::PATH_SEPARATOR, $this->unifyPath($requestPath));
        while (!empty($path)) {
            $segment = array_shift($path);
            $nextNode = null;

            foreach ($node->nodes as $key => $subnode) {
                if (substr($key, 0, 1) == "{" && substr($key, -1) == "}") {
                    $converterName = substr($key, 1, -1);
                    $converter = $this->container->getObject($converterName);
                    if ($converter->accepts($segment)) {
                        $args[] = $converter->convert($segment);
                        $nextNode = $subnode;
                    }
                } elseif ($segment == $key) {
                    $nextNode = $subnode;
                }

                if ($nextNode) {
                    break;
                }
            }

            if (!$nextNode) {
                throw new NoRouteException($requestPath, $requestMethod);
            }

            $node = $nextNode;
        }

        $controller = $this->container->getObject($node->action[0]);
        return array($controller, new \ReflectionMethod($controller, $node->action[1]), $args);
    }
    
    // TODO: ADD PARAMETERS AND REVERSE CONVERTERS!
    // TODO: FILTER OUT POST REQUESTS!
    public function getActionUrl($action) {
        if(!isset($this->reverseRoutes[$action])) {
            throw new \Exception("Unknown action: " . $action);
        }
        
        $node = $this->reverseRoutes[$action];
        assert(!is_null($node->route));
        return "/" . $node->route;
    }

    private function addRoute($route, $action) {
        // extract method first
        $arr = explode(self::METHOD_DELIMITER, $route);
        assert(count($arr) <= 2);
        $path = explode(self::PATH_SEPARATOR, $this->unifyPath($arr[0]));
        $method = count($arr) == 2 ? $arr[1] : "GET";
        assert($method == "GET" || $method == "POST");
        $controllerAction = explode(self::PATH_SEPARATOR, $action);
        assert(count($controllerAction) == 2);

        if (!array_key_exists($method, $this->preprocessedRoutes)) {
            $this->preprocessedRoutes[$method] = new RouteNode();
        }

        $node = $this->preprocessedRoutes[$method];

        while (!empty($path)) {
            $segment = array_shift($path);
            if (!array_key_exists($segment, $node->nodes)) {
                $newNode = new RouteNode();
                $newNode->parent = $node;
                $node->nodes[$segment] = $newNode;
            }

            $node = $node->nodes[$segment];
        }

        assert($node->action == null);
        $node->action = $controllerAction;
        $node->route = $route;
        
        assert(!isset($this->reverseRoutes[$action]));
        $this->reverseRoutes[$action] = $node;
    }

    private function unifyPath($path) {
        if (substr($path, -1) == self::PATH_SEPARATOR) {
            $path = substr($path, 0, -1);
        }

        return $path;
    }

    public function setRoutes($routes) {
        $this->routes = $routes;
    }

    public function setContainer($container) {
        $this->container = $container;
    }

}